package com.yjt.common.redis;

import com.yjt.common.exceptions.RedisLockException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author 曾智平
 * @ClassName: RedisLocalAspect
 * @Description: redis锁切面
 * @date 2021/9/30 10:01
 */
@Aspect
@Component
@Slf4j
public class RedisLockAspect {


    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 执行顺序在before之前
     *
     * @param proceedingJoinPoint
     */
    @Around("execution(public * *(..)) && @annotation(com.yjt.common.redis.StockLock)")
    public Object setRedosOper(ProceedingJoinPoint proceedingJoinPoint) {
        //获取方法签名
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();
        StockLock stockLock = method.getAnnotation(StockLock.class);
        //生成锁的key
        String lockKey = this.getLockKey(proceedingJoinPoint, method, stockLock);
        //加锁
        return this.lock(lockKey, stockLock.expireTime(), proceedingJoinPoint);
    }


    /**
     * 加锁
     *
     * @param key
     * @param expireTime
     */
    public Object lock(String key, long expireTime, ProceedingJoinPoint proceedingJoinPoint) {
        Object proceed = null;
        //原子性执行
        log.info("====================开始获取锁：{}==================", key);
        boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, 1, expireTime, TimeUnit.SECONDS);
        if (isLock) {
            try {
                log.info("====================进入锁：{}==================", key);
                log.info("处理业务中");
                proceed = proceedingJoinPoint.proceed();
            } catch (Throwable throwable) {
                log.info("业务执行出现问题-{}", throwable.getMessage());
            } finally {
                // 业务异常，手动释放锁
                redisTemplate.delete(key);
                log.info("------------redis锁释放成功-----------");
            }
        } else {
            log.info("获取不到锁,抛出异常信息");
            throw new RedisLockException("业务正在处理中,请稍等...");
        }
        return proceed;
    }


    /**
     * 获取锁key
     *
     * @param pjp
     * @return
     */
    public String getLockKey(ProceedingJoinPoint pjp, Method method, StockLock stockLock) {
        //获取方法参数
        Object[] args = pjp.getArgs();
        Parameter[] parameters = method.getParameters();
        StringBuilder builder = new StringBuilder();
        builder.append(pjp.getSignature().getDeclaringTypeName());
        builder.append(pjp.getSignature().getName());
        builder.append(stockLock.prefix());
        for (int i = 0; i < parameters.length; i++) {
            Object arg = args[i];
            if (arg instanceof Map) {
                Map<String, Object> temArgMap = (Map<String, Object>) arg;
                for (Object value : temArgMap.values()) {
                    if (builder.length() > 0) {
                        builder.append(stockLock.delimiter());
                    }
                    builder.append(value);
                }
            }
            builder.append(arg);
        }
        return builder.toString();
    }

}
